package com.wzh.yuvwater.encoder;

import android.annotation.TargetApi;
import android.graphics.Bitmap;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;

import com.wzh.yuvwater.jni.YuvOsdUtils;
import com.wzh.yuvwater.muxer.MuxerData;
import com.wzh.yuvwater.muxer.MuxerManager;
import com.wzh.yuvwater.utils.BitmapUtils;
import com.wzh.yuvwater.utils.Logger1;
import com.wzh.yuvwater.utils.TimeStaticUtils;
import com.wzh.yuvwater.utils.YuvUtils;

import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.concurrent.ArrayBlockingQueue;

@TargetApi(Build.VERSION_CODES.M)
public class VideoCodecManager {
    public static final boolean DEBUG = false;

    private static final String TAG = "VideoCodecManager";
    // parameters for the encoder
    private static final String MIME_TYPE = "video/avc"; // H.264 Advanced Video
    private static VideoCodecManager sInstance;

    private ArrayBlockingQueue<byte[]> frameBytes;
    private MediaCodec mMediaCodec;

    private int mColorFormat;
    private MediaFormat mediaFormat;
    private volatile boolean isStart = false;
    private boolean isAddOSD = true;
    private int dstWidth, dstHeight;

    private Handler mHandler;
    private HandlerThread mHandlerThread;
    private SimpleDateFormat dateFormat;

    private boolean isInitCodec;
    private boolean isFlush = false;
    private boolean isHasKeyFrame;      //是否存在关键帧

    private int off_y = 20, off_x = 50;
    private int rotation;

    private MuxerManager mMuxerManager;
    private MuxerManager.VideoCallback videoCallback;

    //通过加锁的方式解决MediaCodec编码器已关闭，视频还在处理导致的异常
    //加锁逻辑:处理视频加锁，操作MediaCodec加锁（保证MediaCodec处理视频正常）
    private Object isStartLock = new Object();

    private VideoCodecManager() {
        mHandlerThread = new HandlerThread("codecThread");
        mHandlerThread.start();
        mHandler = new Handler(mHandlerThread.getLooper());
    }

    public static VideoCodecManager getInstance() {
        if (sInstance == null) {
            synchronized (VideoCodecManager.class) {
                if (sInstance == null) {
                    sInstance = new VideoCodecManager();
                }
            }
        }
        return sInstance;
    }

    public void setVideoCallback(MuxerManager.VideoCallback videoCallback) {
        this.videoCallback = videoCallback;
    }

    /**
     * 初始化编码器
     *
     * @param dstWidth
     * @param dstHeight
     */
    public void initCodecManager(int dstWidth, int dstHeight, int rotation) {
        this.dstWidth = dstWidth;
        this.dstHeight = dstHeight;
        this.rotation = rotation;

        this.isFlush = false;

        if (!isInitCodec) {
            Logger1.i(TAG, "initCodecManager: width=%s*%s rotation=%s", dstWidth, dstHeight, rotation);
            mHandler.post(() -> {
                mMuxerManager = MuxerManager.getInstance();
                frameBytes = new ArrayBlockingQueue<>(100);

                prepare();
                String pattern = "yyyy-MM-dd HH:mm:ss";//日期格式
                dateFormat = new SimpleDateFormat(pattern, Locale.CHINA);

                YuvOsdUtils.initOsd(off_x, off_y, pattern.length(), dstWidth, dstHeight, rotation);

                isInitCodec = true;
            });
        }
    }

    /**
     * 开始编码器
     */
    public void startMediaCodec() {
        if (isStart) {
            Logger1.i(TAG, "startMediaCodec: was started");
            return;
        }
        mHandler.post(() -> start());
    }

    /**
     * 暂停录制
     */
    public void pauseMediaCodec() {
        if (!isStart) {
            Logger1.i(TAG, "MediaCodec: isn't started");
            return;
        }
        frameBytes.clear();
    }

    /**
     * 继续录制
     */
    public void resumeMediaCodec() {
        if (!isStart) {
            Logger1.i(TAG, "MediaCodec: was started");
            return;
        }
    }

    public void stopRecording() {
        if (mMediaCodec != null) {
            //注意顺序，否则可能导致停止一场
            frameBytes.clear();
            mHandlerThread.quit();
            YuvOsdUtils.releaseOsd();
            stopMediaCodec();
            sInstance = null;
        }
    }

    public void addFrameData(byte[] data) {
        if (isStart && !PauseManager.getInstance().isPause()) {
            //offer添加一个数据
            boolean isOffer = frameBytes.offer(data);
            if (!isOffer) {
                //如果添加不成功，则移除一个数据后再添加
                frameBytes.poll();
                frameBytes.offer(data);
            }
        }
    }

    /**
     * 准备一些需要的参数
     * <p>
     * YV12: YYYYYYYY VVUU    =>YUV420P
     * NV12: YYYYYYYY UVUV     =>YUV420SP
     * NV21: YYYYYYYY VUVU     =>YUV420SP
     * create at 2017/3/22 18:13
     */
    private void prepare() {
        mColorFormat = MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible;//nv12 最终数据需要nv12

        int videoW = rotation == 90 || rotation == 270 ? dstHeight : dstWidth;
        int videoH = rotation == 90 || rotation == 270 ? dstWidth : dstHeight;
        int frameRate = 10; // 10fps
        int compressRatio = 256;
        //宽*高*rgb颜色3位*8位*帧率
        int bitRate = dstWidth * dstHeight * 3 * 8 * frameRate / compressRatio;

        //注意这里旋转后有一个大坑，就是要交换mHeight，和mWidth的位置。否则录制出来的视频时花屏的。
        mediaFormat = MediaFormat.createVideoFormat(MIME_TYPE, videoW, videoH);
        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate);
        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, mColorFormat);
        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
        Logger1.d(TAG, "prepare format: " + mediaFormat);
    }

    private void start() {
        if (!isInitCodec)
            throw new RuntimeException("initCodec is false,please call initCodecManager() before");
        if (isStart) {
            Logger1.i(TAG, "startMediaCodec: was started");
            return;
        }
        try {
            Logger1.i(TAG, "startMediaCodec: starting");

            mMediaCodec = MediaCodec.createEncoderByType(MIME_TYPE);
            mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
            mMediaCodec.setCallback(getCodecConfig(), mHandler);
            mMediaCodec.start();
        } catch (Exception e) {
            e.printStackTrace();
            return;
        }
        isStart = true;
    }

    /**
     * 对收到的图像添加水印
     */
    private byte[] addImageOsd(byte[] rawData) {
        boolean isView = false;
        if (isView) {
            Bitmap rawBitmap = BitmapUtils.nv21ToBitmap(rawData.clone(), dstWidth, dstHeight);
        }

        //生成镜像
        byte[] mirrorNv21Data = YuvUtils.nv21Mirror(rawData.clone(), dstWidth, dstHeight);
        if (isView) {
            Bitmap mirrorBitmap = BitmapUtils.nv21ToBitmap(mirrorNv21Data.clone(), dstWidth, dstHeight);
        }
        //添加时间戳
        byte[] timeData = rawData.clone();
        String dateStr = dateFormat.format(new Date());
        YuvOsdUtils.addOsd(mirrorNv21Data, timeData, dateStr);
        //内容为nv12（直接通过nv21转bitmap绿屏）
        if (isView) {
            Bitmap timeBitmap = BitmapUtils.nv21ToBitmap(timeData.clone(), dstWidth, dstHeight);
        }
        return timeData;
    }

    private MediaCodec.Callback getCodecConfig() {
        return new MediaCodec.Callback() {
            @Override
            public void onInputBufferAvailable(MediaCodec mediaCodec, int index) {
                byte[] outData;
                byte[] rawData;
                try {
                    //注意有isStartLock锁，此处阻塞不应该在锁之中！
                    byte[] bytes = frameBytes.take();
                    rawData = bytes.clone();
                    outData = new byte[rawData.length];
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    return;
                }

                //此段内容处理时间比较长，不加锁
                if (isAddOSD) {
                    TimeStaticUtils staticUtils = TimeStaticUtils.start();
                    outData = addImageOsd(rawData);
                    Logger1.i(TAG, "图像处理时间" + staticUtils.endAndGetRunTime());
                    if (videoCallback != null) {
                        videoCallback.after(outData.clone());
                    }
                } else {
                    YuvOsdUtils.nv21ToNv12(rawData, outData, dstWidth, dstHeight);
                }

                synchronized (isStartLock) {
                    if (!isStart) {
                        return;
                    }
                    ByteBuffer inputBuffer = mediaCodec.getInputBuffer(index);
                    inputBuffer.clear();
                    //停止录像后，buffer is inaccessible
                    inputBuffer.put(outData);
                    //通过控制时间轴，达到暂停录制，继续录制的效果
                    long pauseTimeLength = PauseManager.getInstance().getPauseTimeLength();
                    long currentTimeUs = PauseManager.getMicTime() - pauseTimeLength;
                    mediaCodec.queueInputBuffer(index, 0, outData.length, currentTimeUs, 0);
                }
            }

            @Override
            public void onOutputBufferAvailable(MediaCodec mediaCodec, int index, MediaCodec.BufferInfo bufferInfo) {
                synchronized (isStartLock) {
                    if (!isStart) {
                        return;
                    }
                    if (!isHasKeyFrame && bufferInfo.flags == MediaCodec.BUFFER_FLAG_KEY_FRAME) {
                        isHasKeyFrame = true;
                    } else if (bufferInfo.presentationTimeUs < PauseManager.getInstance().getLastPauseTime() || !isHasKeyFrame) {
                        //上一视频的数据，或者无关键帧，丢弃
                        //视频第一帧一定要是关键帧
                        Logger1.i(TAG, "onOutputBufferAvailable: discard %s time=%s", isHasKeyFrame, bufferInfo.presentationTimeUs);
                    } else if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
                        Logger1.d(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG");
                    } else {
                        ByteBuffer outputBuffer = mediaCodec.getOutputBuffer(index);
                        outputBuffer.position(bufferInfo.offset);
                        outputBuffer.limit(bufferInfo.offset + bufferInfo.size);

                        MuxerData muxerData = new MuxerData();
                        muxerData.setTrackIndex(MuxerManager.TRACK_VIDEO);
                        muxerData.setBufferInfo(bufferInfo);
                        muxerData.setByteBuf(outputBuffer);

                        Date date = new Date();
                        date.setTime(bufferInfo.presentationTimeUs / 1000);
                        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA);
                        String format = dateFormat.format(date);
                        Logger1.i("onSendBufferVideo", format);

                        mMuxerManager.sendWriteVideoDataMsg(muxerData);
                    }
                    mediaCodec.releaseOutputBuffer(index, false);
                }
            }

            @Override
            public void onError(MediaCodec codec, MediaCodec.CodecException e) {
                Logger1.e(TAG, "VideoCodecOnError", e);
            }

            @Override
            public void onOutputFormatChanged(MediaCodec mediaCodec, MediaFormat format) {
                MediaFormat newFormat = mMediaCodec.getOutputFormat();
                mMuxerManager.sendAddTrackMsg(MuxerManager.TRACK_VIDEO, newFormat);
            }
        };
    }

    /**
     * 刷新（清理数据，重写视频帧等）
     */
    public synchronized void flushMediaCodec() {
        Logger1.i(TAG, "flushMediaCodec");
        frameBytes.clear();
        isFlush = true;
        //  lastPauseTime = getMicTime();
        isHasKeyFrame = false;
    }

    private void stopMediaCodec() {
        //这里操作了mMediaCodec，必须加锁
        synchronized (isStartLock) {
            isStart = false;
        }
        if (mMediaCodec != null) {
            mMediaCodec.stop();
            mMediaCodec.release();
            mMediaCodec = null;
        }
        Logger1.i(TAG, "stopMediaCodec video");
    }

}
